跳至主要内容

課程:Redux 思維與 RTK 核心 第 1 堂:Redux 思維建立

01狀態管理的必要性

在你開始學習任何 Redux Toolkit 的語法之前,我們必須先問一個最根本的問題:「為什麼我們需要它?」

如果你曾經開發過小型 React 專案,你可能會覺得 useStateuseEffect 已經綽綽有餘。甚至當專案稍微變大時,React 內建的 Context API 似乎也能解決大部分的問題。那麼,為什麼 Redux 依然是許多大型專案的首選?為什麼我們需要引入一個看起來充滿「樣板程式碼(Boilerplate)」且增加系統複雜度的工具?

這並非因為工程師喜歡把簡單的事情變複雜,而是為了應對當 React 應用程式成長到一定規模後,資料流(Data Flow)會變得混亂且難以預測的本質問題。

消失的資料:Props Drilling 的結構性痛點

讓我們從 React 最核心的設計模式說起。React 是單向資料流(Unidirectional Data Flow),資料總是透過 Props 由父元件傳遞給子元件。

想像一個場景

假設你正在開發一個電商平台。你的應用程式最頂層有一個 App 元件,裡面存儲著「當前登入的使用者資訊(User Object)」。

  1. App 需要把這個 User 資訊傳給 Header 元件。
  2. Header 裡面有一個 Navbar 元件。
  3. Navbar 裡面又有一個 UserMenu 元件。
  4. UserMenu 裡面還有一個 Avatar 元件,這才是最後真正需要顯示使用者頭像的地方。

為了讓 Avatar 顯示圖片,你必須在 HeaderNavbarUserMenu 這些中間元件中,全都寫上 user={user}

這就是惡名昭彰的 Props Drilling(Props 鑽孔)

為什麼這是一個問題?

當你只有三層元件時,這看起來只是多打幾個字。但當專案規模擴大到十幾層,或者同一個資料需要被傳遞到樹狀結構的各個角落時,問題就會爆發:

  • 維護成本極高:如果你決定把 user 物件中的 id 欄位改名為 uid,你可能需要修改 10 個檔案,只為了確保這筆資料能正確「流過」那些根本不關心它的中間元件。
  • 元件純粹性被破壞:中間的 Navbar 其實完全不需要知道使用者的資訊,它的職責只是排版。但因為 Props Drilling,它被迫承擔了傳遞資料的責任。這使得元件難以被獨立提取或複用。
  • 除錯困難:當 Avatar 顯示的圖片錯誤時,你必須沿著這條長長的隧道往回找,確認是在哪一層元件、在哪個環節資料被不小心修改或遺失了。

向上提升狀態的侷限:當「神元件」出現

面對 Props Drilling,React 官方文件的建議通常是「Lifting State Up(提升狀態)」。也就是將狀態移動到需要該資料的所有元件的「最近共同祖先」中。

這在初期的確能解決問題,但隨著功能增加,這種做法會遇到嚴重的瓶頸。

1. 頂層元件的臃腫化

如果你的應用程式有許多共享狀態(使用者資訊、購物車內容、佈景主題、通知訊息、權限設定),最終所有的狀態都會被「提升」到最頂層的 App.tsxMainLayout.tsx 中。

這些頂層元件會變成所謂的「神元件(God Component)」。它們管理著數十個 useState,處理著各種複雜的更新邏輯。這導致單一檔案可能突破數千行,邏輯糾纏在一起,只要改動其中一小塊,整個系統都可能受影響。

2. 無關元件的重複渲染(Re-render)效能問題

在 React 的預測機制中,當一個元件的 State 改變時,該元件及其所有的子元件預設都會重新執行渲染函數。

假設你的 App 元件同時管理著「購物車數量」和「使用者名稱」。當使用者僅僅是往購物車裡加了一個東西,App 的 State 更新了,導致整個 App 重新渲染。這意味著即使 UserMenu 完全不需要知道購物車的變化,它也會跟著被重新計算一次。

雖然 React 虛擬 DOM 的機制很快,但在大型應用中,這種無意義的連鎖重新渲染累積起來,會明顯拖慢網頁效能,導致打字卡頓或動畫不流暢。

Context API 是萬靈丹嗎?

有些開發者會說:「我們有 Context API 呀!它不就是為了解決 Props Drilling 而生的嗎?」

沒錯,Context API 確實解決了「傳遞」的問題。它像是一個傳送門,讓你可以在頂層放資料,底層直接接收,繞過中間層。但 Context API 在作為「全域狀態管理工具」時,有幾個致命的侷限。

1. 缺乏精確的訂閱機制

Context 的最大問題在於:只要 Context 的 Value 發生變化,所有使用了 useContext 該 Context 的元件都會被強制重新渲染。

如果你在一個 Context 裡放了 10 個不同的屬性,當屬性 A 改變時,那些只關心屬性 B 的元件也會跟著重新渲染。在 React 中,要優化 Context 的效能非常繁瑣,你可能需要把 Context 切得很碎,或是搭配複雜的 useMemo

2. 缺乏開發者工具與可追蹤性

當你的狀態變得很複雜,且有多個地方在同時更新狀態時,Context 就像是一個「黑盒子」。你很難知道:

  • 是誰在什麼時候修改了資料?
  • 資料修改的前後差異是什麼?
  • 我們能否「回到過去」看看五秒前的狀態?

在大型專案中,「可預測性」比「方便性」更重要。Context 提供了方便,但在複雜邏輯下,它無法提供像 Redux 那樣強大的開發者工具(Redux DevTools),讓我們能像看錄影帶一樣回放每一次的狀態變更。

我們何時真正需要 Redux?

學習 Redux 並不是為了取代 useState。事實上,大多數的 UI 狀態(例如:表單輸入框的內容、選單是否開啟)依然應該保留在元件內部的 useState 中。

你應該在以下幾種情境下考慮使用 Redux:

  1. 狀態具有「全球性」且被頻繁使用: 例如使用者的登入狀態、權限設定、全域的載入中(Loading)狀態。這些資料需要在應用的各個角落被讀取,且變動後需要同步更新所有地方。
  2. 狀態更新邏輯極度複雜: 如果一個狀態的改變涉及到多個步驟,或者取決於另一個狀態的舊值(例如:複雜的撤銷/重做 Undo/Redo 功能,或是像 Trello 那種看板的任務拖拽邏輯),Redux 的 Reducer 模式能將邏輯從 UI 中抽離,讓測試和維護變得容易。
  3. 需要強大的除錯支援: 如果你在開發一個對資料正確性要求極高的系統(如金融交易介面),Redux 的「時間旅行除錯(Time Travel Debugging)」功能能讓你精確掌握每一筆資料的來龍去脈。
  4. 團隊協作的需求: Redux 強制執行一套嚴格的規範。雖然初學時覺得麻煩,但這意味著團隊中任何一個成員看到一段 Redux 程式碼,都能立即明白資料流向。它提供了一種「架構上的共識」。

總結

狀態管理的必要性,源於對「軟體複雜度」的控制。React 給了我們強大的 UI 元件化能力,但當元件之間的資料交換變得像一團亂掉的毛線球時,我們需要一套更科學、更可預測的方法來管理這些資料。

Redux 並不神奇,它只是把「狀態」從 React 元件樹中抽離出來,放在一個獨立的容器裡,並規定了一套嚴格的遊戲規則來存取它。

在下一章中,我們將深入探討這套遊戲規則的基石——Redux 的三大原則。理解了這些原則,你就會明白為什麼 Redux 要設計得如此「古怪」,以及它是如何保證狀態永遠是可預測的。

關鍵要點與下一章預告

本章核心回顧

  • Props Drilling:跨多層傳遞資料導致中間元件被迫處理無關資訊,增加維護難度與出錯風險。
  • Lifting State Up 的極限:狀態提升到頂層會導致元件過於臃腫(神元件),並引發不必要的整棵元件樹重新渲染。
  • Context API 的侷限:雖然能繞過中間層,但在效能優化(缺乏精確訂閱)與開發者工具(缺乏可追蹤性)上不如專門的狀態管理庫。
  • Redux 的角色:它不是萬靈丹,而是針對複雜、全域、需要高度可預測性狀態的解決方案。

銜接下一個主題

理解了狀態管理的「痛點」後,你可能會想:那 Redux 是怎麼解決這些問題的?它的解決方案是建立在一套嚴謹的哲學之上的。下一章我們將探討 Redux 的三大原則,這將揭示為什麼 Redux 要求我們把所有的狀態都放在同一個地方,以及為什麼它規定你絕對不能直接修改狀態。這些「限制」正是 Redux 強大的來源。